smt2: flatten FPA-encoded floats to their IEEE bit pattern#9031
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates smt2_convt::flatten2bv to support flattening floatbv expressions when using the SMT-LIB FloatingPoint (FPA) theory, addressing crashes when a program type-puns floats (e.g., union/byte-access to read IEEE bit patterns) under --smt2 --fpa.
Changes:
- Add an FPA-specific
flatten2bvpath forfloatbvthat can emit IEEE bit-pattern bit-vectors for float constants. - Recognize and flatten the common “bit-pattern reinterpret” shape
(float)bitsby emitting the underlying same-width bit-vector operand. - Replace the previous blanket
!use_FPA_theoryinvariant with a narrower “unsupported shape” failure for non-constant, non-reinterpret FPA floats.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #9031 +/- ##
========================================
Coverage 80.59% 80.59%
========================================
Files 1713 1713
Lines 189403 189427 +24
Branches 73 73
========================================
+ Hits 152647 152669 +22
- Misses 36756 36758 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Can this be generalised beyond constants by using the FPA to bit vector conversion operator? |
The general case is already handled via If we want to handle some future scenario where such a non-constant conversion would not be subject to |
|
Ok, at the very least the description should be changed; the SMT-LIB FP theory does have a float-to-bit-vector operator, namely |
|
And I would probably add some narrative why these aren't used. |
Good point — fixed the wording. |
| // from type-punning (union / byte access) without such an operator: | ||
| // - a constant: the IEEE interchange bit pattern is the value's | ||
| // bit-vector representation, emitted as a literal; | ||
| // - a bit-pattern reinterpret `(float)bits` (a typecast from a |
There was a problem hiding this comment.
The narrative is still very confusing. (float)bits looks syntactically like a C type cast, which is of course a value conversion, and not a bit-pattern reinterpretation.
| const auto &tc = to_typecast_expr(expr); | ||
| if( | ||
| tc.op().type().id() == ID_bv && | ||
| boolbv_width(tc.op().type()) == boolbv_width(type)) |
There was a problem hiding this comment.
BTW, not least owing to this confusion, I am considering to replace the double cast via bv_typet by a new operator, named say reinterpret_cast.
There was a problem hiding this comment.
Isn't that where we usually use byte_extract or extractbits?
There was a problem hiding this comment.
Thinking about this again the double cast quite possibly is the result of lowering a byte_extract. Let me see if we can replace that by extractbits.
There was a problem hiding this comment.
Suppose you see an extracbits that extracts all bits. Wouldn't that confuse you?
There was a problem hiding this comment.
Or you would expect the simplifier to remove it?
There was a problem hiding this comment.
Indeed, simplify_extractbits would rewrite it straight back into a typecast. So we'd need something like a reinterpret_cast to clean this up, but actually the simpler solution is to just remove this part from the PR (as done in 201c968): just flattening float constants to their IEEE bit pattern, because this is the only branch that's truly reachable here, see also #9031 (comment).
There was a problem hiding this comment.
I'll do a separate PR with a reinterpret_cast_exprt.
smt2_convt::flatten2bv is reached when a value has to be turned into a
plain bit-vector, e.g. at a union/byte-access (type-punning) boundary.
For a floatbv operand encoded with the SMT-LIB FloatingPoint theory it
previously asserted !use_FPA_theory and gave up. As a result any
program that reads the raw bytes of an FPA-encoded float constant under
an FPA solver (--cprover-smt2, or --z3/--cvc5/--bitwuzla with --fpa) hit
an invariant violation -- for example reading a double's bit pattern via
a union { double d; uint64_t i; }.
A floatbv constant's IEEE-754 interchange bit pattern is exactly its
bit-vector representation (NaN payloads and signed zero included), so it
is now emitted as a literal bit-vector. This is the only shape that
reaches flatten2bv under FPA: a non-constant float whose bits are read
is lowered by lower_byte_operators into a float typecast, which is
handled by the bvfromfloat round-trip in find_symbols and never reaches
flatten2bv. Any other (genuinely symbolic) float arriving here is left
as a clearly-reported UNEXPECTEDCASE rather than the previous blanket
invariant.
SAT mode is unaffected: use_FPA_theory is false there, so the existing
convert_expr(expr) path is taken unchanged.
CI coverage is provided by a unit test in unit/solvers/smt2/smt2_conv.cpp
that drives flatten2bv directly under solvert::CPROVER_SMT2
(use_FPA_theory == true) with an FPA-encoded `double` constant; it
SIGABRTs on the previous invariant if the fix is reverted. The
companion regression test under regression/cbmc/union-double-bits-fpa/
documents the user-visible union-based scenario; CPROVER's in-tree SMT2
solver does not fully support the SMT-LIB FloatingPoint theory beyond
constant folding, so that test is tagged `broken-cprover-smt-backend`,
but it has been manually confirmed that with the new flatten2bv emission
the formula is solver-compatible under `--z3 --fpa`.
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
smt2_convt::flatten2bv is reached when a value has to be turned into a plain bit-vector, e.g. at a union/byte-access (type-punning) boundary. For a floatbv operand encoded with the SMT-LIB FloatingPoint theory it previously asserted !use_FPA_theory and gave up, because that theory has no float-to-bit-vector operation. As a result any program that reads the raw bytes of an FPA-encoded float under an FPA solver (--cprover-smt2, or --z3/--cvc5/--bitwuzla with --fpa) hit an invariant violation -- for example reading a double's bit pattern via a union { double d; uint64_t i; }.
Two shapes cover the cases that actually arise from type-punning, and both are emitted without needing a float-to-bit-vector operation:
A non-constant FPA float that is not such a reinterpret would require the bvfromfloat round-trip (a fresh bit-vector b with to_fp(b) = x), which has to be registered by find_symbols; that case does not arise from the byte-/union-lowering paths and is left as a clearly-reported unsupported case rather than the previous blanket invariant.
SAT mode is unaffected: use_FPA_theory is false there, so the existing convert_expr(expr) path is taken unchanged.